// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.framework.utils;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.UUID;
import java.util.Vector;

import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.dbpool.DBShell;

/**
 * 单号(order number)生成实用类.<br>
 * 生成的单号形如：“CGDD201101140004”这样的由日期和流水号组成的字符串.<br>
 * 单号生成规则定义于数据库表esys_on里.
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public class ONHelper {
    /**
     * 单号生成配置信息存放表名
     */
    private static String dhConfTableName = "esys_on";//TableConst.DEFAULT_T_SYS_DH_GEN;
    /**
     * 数据库操作实用类
     */
    private static DBShell db = new DBShell();
    /**
     * 单号信息缓存,用于提高访问的效率,后台更新配置后必须重新启动服务后才可使新设置生效
     */
    private static HashMap<String, ESYSOrderNumberDTO> orderNumbersConfCache = new HashMap();

    public static String DHMC = "sales_so_order_id";

    /**
     * 获得单个单号.<br>
     * 最终生成的日期和流水号检查日期都是使用当前系统日期.
     *
     * @param dhmc 单号名称
     * @return 新的单号
     * @throws Exception 格式问题,单号规则未定义问题,最大值问题
     *                   {@link #getDh(String, String, String)}
     */
    public static String getON(String dhmc) throws Exception {
        if (CommonUtils.isStringEmpty(dhmc)) {
            dhmc = ONHelper.DHMC;
        }
        return getON(dhmc, null, null);
    }

    public static String getONEasy(String dhmc) throws Exception {
        if (CommonUtils.isStringEmpty(dhmc)) {
            dhmc = ONHelper.DHMC;
        }
        ESYSOrderNumberDTO dhxx = (ESYSOrderNumberDTO) orderNumbersConfCache.get(dhmc);
        if (dhxx == null) {
            dhxx = getONConf(dhmc);
            orderNumbersConfCache.put(dhmc, dhxx);
        }
        String on = dhxx.getHeadIdent() + dhxx.getInterval1() + System.nanoTime() + dhxx.getInterval2() + UUID.randomUUID();
        return on;
    }

    /**
     * 批量获得多个单号.<br>
     * 最终生成的日期和流水号检查日期都是使用当前系统日期.
     *
     * @param dhmc 单号名称
     * @param cnt  要批量生成的数量
     * @return @return 一维数组结构的单号串
     * @throws Exception
     * @see {@link #getMultiDh(String, String, String, int)}
     */
    public static String[] getMultiON(String dhmc, int cnt) throws Exception {
        return getMultiON(dhmc, null, null, cnt);
    }

    /**
     * 获得单个单号.<br>
     * 单号形如：“NCG-110119-0006”，那么 “NCG-110119-”就是前缀字符串.
     *
     * @param dhmc    要生成单号在数据库中配置名（用以取出该单号的配置信息，以备生成时使用）
     * @param dhRq    单号的日期部份（本日期用来生成单号中的前缀字符串的日期部分，本字段为null则自动取当前系统日期，正如上面的 “110119”）
     * @param checkRq 一般情况下当表dhscxx中的，scrq和lshrqgs一样时，本参数=dhRq（当不相等时
     *                用于有的单号，单号要到日，但流水号却是按月来的），取最终的流水号是用LIKE '"+checkRqStr+"%'语句实现的，
     *                因而利用lshrqgs的设置可以实现最终生成的单号日期字段是到日但是流水号却是按月来统计的（本字段为null则自动取当前系统日期，正如上面的 “110119”）
     * @return @return 一个单号串
     * @throws Exception
     * @see {@link #getMultiDh(String, String, String, int)}
     */
    public static String getON(String dhmc, String strrq, String strjcrq) throws Exception {
        String[] ret = getMultiON(dhmc, strrq, strjcrq, 1);
        return ret == null || ret.length <= 0 ? null : ret[0];
    }

    /**
     * 批量获得多个单号.<br>
     * 单号形如：“NCG-110119-0006”，那么 “NCG-110119-”就是前缀字符串.
     *
     * @param dhmc    要生成单号在数据库中配置名（用以取出该单号的配置信息，以备生成时使用）
     * @param dhRq    单号的日期部份（本日期用来生成单号中的前缀字符串的日期部分，本字段为null则自动取当前系统日期，正如上面的 “110119”）
     * @param checkRq 一般情况下当表dhscxx中的，scrq和lshrqgs一样时，本参数=dhRq（当不相等时
     *                用于有的单号，单号要到日，但流水号却是按月来的），取最终的流水号是用LIKE '"+checkRqStr+"%'语句实现的，
     *                因而利用lshrqgs的设置可以实现最终生成的单号日期字段是到日但是流水号却是按月来统计的（本字段为null则自动取当前系统日期，正如上面的 “110119”）
     * @param cnt     要批量获得的条数
     * @return @return 一维数组结构的单号串
     * @throws Exception
     * @see {@link #getMultiDhImpl(String, String, String, String, int, int)}
     */
    public static String[] getMultiON(String dhmc, String dhRq, String checkRq, int cnt) throws Exception {
        ESYSOrderNumberDTO dhxx = (ESYSOrderNumberDTO) orderNumbersConfCache.get(dhmc);
        if (dhxx == null) {
            dhxx = getONConf(dhmc);
            orderNumbersConfCache.put(dhmc, dhxx);
        }
        //最终生成的单号上的日期（如果指明日期为null则取当前日期）
        if (dhRq == null)//空取系统日期
            dhRq = new SimpleDateFormat(dhxx.getDatePattern()).format(new Date());
        //检查日期格式-流水号的日期（如果指明检查日期为null则取当前日期）
        if (checkRq == null)
            checkRq = new SimpleDateFormat(dhxx.getFlowNumDatePattern()).format(new Date());
        if (dhRq.length() < checkRq.length())
            throw new RuntimeException("dh生成时最终单号日期格式不可小于用于流水号检查的局部日期格式.");
        if (dhRq.length() > checkRq.length()) {
            for (int i = 0; i < dhRq.length() - checkRq.length() + 1; i++)
                checkRq = checkRq + "_";
        }
        String[] sns = getMultiONImpl(dhxx.getTableName(), dhxx.getFieldName(), dhxx.getHeadIdent() + dhxx.getInterval1() + dhRq + dhxx.getInterval2()
                , dhxx.getHeadIdent() + dhxx.getInterval1() + checkRq + dhxx.getInterval2(), dhxx.getFlowNumLen(), cnt);
        return sns;
    }

    /**
     * 获得一个单号.<br>
     * 单号形如：“NCG-110119-0006”，那么 “NCG-110119-”就是前缀字符串.
     *
     * @param table      要生成单号的表（用于最后的流水号的生成）
     * @param field      要生成单号的表字段名（用于最后的流水号的生成）
     * @param prefixStr  前缀字符串（生成的完整单号前面那一串，正如上面的 “NCG-110119-”）
     * @param checkRqStr 一般情况下当表dhscxx中的，scrq和lshrqgs一样时，本参数=prefixStr（当不相等时
     *                   用于有的单号，单号要到日，但流水号却是按月来的），取最终的流水号是用LIKE '"+checkRqStr+"%'语句实现的，
     *                   因而利用lshrqgs的设置可以实现最终生成的单号日期字段是到日但是流水号却是按月来统计的
     * @param flowNumLen 流水号长度
     * @return 一个单号字符串
     * @throws Exception
     * @see {@link #getMultiDhImpl(String, String, String, String, int, int)}
     */
    public static synchronized String getONImpl(String table, String field, String prefixStr, String checkRqStr, int ws)
            throws Exception {
        String[] ret = getMultiONImpl(table, field, prefixStr, checkRqStr, ws, 1);
        return ret == null || ret.length <= 0 ? null : ret[0];
    }

    /**
     * 批量获得多个单号.<br>
     * 单号形如：“NCG-110119-0006”，那么 “NCG-110119-”就是前缀字符串.
     *
     * @param table      要生成单号的业务单据表（用于最后的流水号的生成）
     * @param field      要生成单号的业务单据表字段名（用于最后的流水号的生成）
     * @param prefixStr  前缀字符串（生成的完整单号前面那一串，正如上面的 “NCG-110119-”）
     * @param checkRqStr 一般情况下当表dhscxx中的，scrq和lshrqgs一样时，本参数=prefixStr（当不相等时
     *                   用于有的单号，单号要到日，但流水号却是按月来的），取最终的流水号是用LIKE '"+checkRqStr+"%'语句实现的，
     *                   因而利用lshrqgs的设置可以实现最终生成的单号日期字段是到日但是流水号却是按月来统计的
     * @param flowNumLen 流水号长度
     * @param cnt        批量生成时要生成的序列号条数
     * @return 一维数组结构的流水号串
     * @throws Exception
     */
    public static synchronized String[] getMultiONImpl(String table, String field, String prefixStr, String checkRqStr
            , int flowNumLen, int cnt) throws Exception {
        if (cnt <= 0)
            throw new Exception("请指定有效的批量生成数量！");

        //例:查询条件query = "0505",而当前数据库中已有0505-001这一编号
        String[] sns = new String[cnt];
        int fLength = prefixStr.length();
//		int qLength = query.length();

        String fildForMax = "";
        if (DBShell.isSQLServer()) {
            fildForMax = "max(convert(int,substring(" + field + "," + (fLength + 1) + "," + flowNumLen + "))) ";
        } else if (DBShell.isMySQL()) {
            fildForMax = "max(convert(substring(" + field + "," + (fLength + 1) + "," + flowNumLen + "),UNSIGNED)) ";
        }

        String sql = "SELECT " + fildForMax + " from " + table + " where " + field + " LIKE '" + checkRqStr + "%'";
        Vector ret = null;
        String wholeSNReturned = "";
        //增加对异常的处理，有错误则直接从1开始编号
        try {
            ret = db.queryData(sql);
            ret = (Vector) ret.get(0);
            wholeSNReturned = ret.get(0).toString();
        } catch (Exception ex) {
        }

        /* 根据位数(ws)生成此位数下最大的可用编号.如ws = 3,则maxOfWs="999" */
        StringBuffer maxOfWs = new StringBuffer();
        for (int i = 0; i < flowNumLen; i++)
            maxOfWs.append("9");
        int maxNum = Integer.parseInt(maxOfWs.toString());

        /* 批量获得流水号 */
        int[] genFlowNums;
        if (wholeSNReturned.equals(""))
            //目前没有0505开头的流水号,从1开始
            genFlowNums = CommonUtils.getFlowingNums(0, 1, cnt);
        else
            genFlowNums = CommonUtils.getFlowingNums(Integer.parseInt(wholeSNReturned), 1, cnt);

        /* 判断流水号的边界是否溢出 */
        if (genFlowNums != null || genFlowNums.length > 0) {
            //若最后一条（即最大值者）已达到此位数下的最大编号,则抛出异常或返回null
            if (genFlowNums[genFlowNums.length - 1] > maxNum)
                throw new Exception("流水号的边界溢出了.");
        }

        /* 一切正常，构造要返回的结果数组 */
        for (int i = 0; i < genFlowNums.length; i++) {
            String sn = new String(prefixStr + CommonUtils.hiString(
                    String.valueOf(genFlowNums[i]), flowNumLen));
            sns[i] = sn;
        }

        return sns;
    }

    /**
     * 获取指定单号配置名的配置信息.
     *
     * @param dhmc 单号配置名
     * @return
     * @throws Exception
     * @see DhscxxDTO
     */
    public static ESYSOrderNumberDTO getONConf(String dhmc) throws Exception {
        String sql = "select " + ESYS_ON.table_name + "," + ESYS_ON.field_name + "," + ESYS_ON.head_ident + ","
                + ESYS_ON.interval1 + "," + ESYS_ON.date_pattern + "," + ESYS_ON.interval2 + "," + ESYS_ON.flow_num_date_pattern
                + "," + ESYS_ON.flow_num_len + " from " + dhConfTableName + " where " + ESYS_ON.on_name + "='" + dhmc + "'";
        Vector retValue = db.queryData(sql);
        if (retValue != null && retValue.size() > 0) {
            Vector anRet = (Vector) retValue.get(0);
            ESYSOrderNumberDTO an = new ESYSOrderNumberDTO();
            an.setOnName(dhmc);
            int i = 0;
            an.setTableName((String) anRet.get(i++));
            an.setFieldName((String) anRet.get(i++));
            an.setHeadIdent((String) anRet.get(i++));
            an.setInterval1((String) anRet.get(i++));
            an.setDatePattern((String) anRet.get(i++));
            an.setInterval2((String) anRet.get(i++));
            an.setFlowNumDatePattern((String) anRet.get(i++));
            an.setFlowNumLen(Integer.parseInt((String) anRet.get(i++)));
            return an;
        }
        throw new java.lang.NullPointerException("单号:" + dhmc + " 没有生成信息!");
    }

    /**
     * 单号生成配置表行数据传输对象.
     *
     * @author js, 2011-01-14
     * @version 1.0
     * @see TableConst#DEFAULT_T_SYS_DH_GEN
     */
    private static class ESYSOrderNumberDTO {
        /**
         * 表格单号生成配置项id
         */
        private String on_name;
        /**
         * 表格名
         */
        private String table_name;
        /**
         * 字段名
         */
        private String field_name;
        /**
         * 头标记
         */
        private String head_ident;
        /**
         * 间隔1
         */
        private String interval1;
        /**
         * 日期格式
         */
        private String date_pattern;
        /**
         * 间隔2
         */
        private String interval2;
        /**
         * 流水号日期格式
         */
        private String flow_num_date_pattern;
        /**
         * 流水号长度
         */
        private int flow_num_len;
        private String bz;

        public String getBz() {
            return bz;
        }

        public void setBz(String bz) {
            this.bz = bz;
        }

        public String getTableName() {
            return table_name;
        }

        public void setTableName(String table_name) {
            this.table_name = table_name;
        }

        public String getOnName() {
            return on_name;
        }

        public void setOnName(String on_name) {
            this.on_name = on_name;
        }

        public String getInterval1() {
            return interval1;
        }

        public void setInterval1(String interval1) {
            this.interval1 = interval1;
        }

        public String getInterval2() {
            return interval2;
        }

        public void setInterval2(String interval2) {
            this.interval2 = interval2;
        }

        public int getFlowNumLen() {
            return flow_num_len;
        }

        public void setFlowNumLen(int flow_num_len) {
            this.flow_num_len = flow_num_len;
        }

        public String getFlowNumDatePattern() {
            return flow_num_date_pattern;
        }

        public void setFlowNumDatePattern(String flow_num_date_pattern) {
            this.flow_num_date_pattern = flow_num_date_pattern;
        }

        public String getDatePattern() {
            return date_pattern;
        }

        public void setDatePattern(String date_pattern) {
            this.date_pattern = date_pattern;
        }

        public String getHeadIdent() {
            return head_ident;
        }

        public void setHeadIdent(String head_ident) {
            this.head_ident = head_ident;
        }

        public String getFieldName() {
            return field_name;
        }

        public void setFieldName(String field_name) {
            this.field_name = field_name;
        }
    }

    // 数据库表esys_on表字段定义（与数据库中的表字段严格对应，请保持一致！）
    public interface ESYS_ON {
        String on_name = "on_name";
        String table_name = "table_name";
        String field_name = "field_name";
        String head_ident = "head_ident";
        String interval1 = "interval1";
        String date_pattern = "date_pattern";
        String interval2 = "interval2";
        String flow_num_date_pattern = "flow_num_date_pattern";
        String flow_num_len = "flow_num_len";
        String bz = "bz";
        String gen_time = "gen_time";
    }
}
